﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;

using Perseus;
using Perseus.Data;
using Perseus.Plugins;
using PLua = Perseus.Scripting.Lua.Lua;

using Hetymine.Executioner.Commands;
using Hetymine.Executioner.Data;
using Hetymine.Executioner.Data.Console;

namespace Hetymine.Executioner.Plugins.Commands.Lua {
    public class Lua : CommandPlugin, ILoader, IExecutableCommands, IConfigurable<IniFile> {
        private LuaConfigurator _Configurator;

        public Lua() {
            this.PluginInfo = PluginInfo.FromAssembly(
                "Lua",
                this.GetType().Assembly,
                "Blake Hancock",
                "http://hetymine.com/executioner/plugins/command/lua/"
            );
        }

        public override ICommand InitializeCommand() {
            return new LuaCommand(this);
        }

        #region Registered Functions
        public void LuaDisplay1(string data) {
            Executioner.Display(data);
        }
        public void LuaDisplay2(string data, int time) {            
            Executioner.Display(data, time);
        }
        public void LuaDisplay3(string data, int time, string display) {            
            Executioner.Display(data, time, display);
        }

        public bool LuaExecute(string command) {
            return Executioner.ExecuteCommand("@execute:" + command);
        }

        public void LuaSoundError() {
            Executioner.Sounds.Error();
        }
        public void LuaSoundErrorNotify() {
            Executioner.Sounds.ErrorNotify();
        }
        public void LuaSoundSucces() {
            Executioner.Sounds.Success();
        }
        public void LuaSoundSuccessNotify() {
            Executioner.Sounds.SuccessNotify();
        }

        public void LuaLog1(string message) {
            App.Log(message);
        }
        public void LuaLog2(string message, string type) {
            MessageType t;
            try {
                t = ((MessageType)Enum.Parse(typeof(MessageType), type, true));
            }
            catch {
                t = MessageType.Unknown;                
            }

            App.Log(message, t);
        }

        public bool LuaIsPluginLoaded(string plugin) {
            return Executioner.IsPluginLoaded(plugin);
        }
        public void LuaPluginInfo(string plugin) {
            var p = Executioner.PluginService[plugin];
            if (p != null) {
                var info = new Dictionary<string, string>();
                info["author"] = p.Instance.PluginInfo.Author;
                info["company"] = p.Instance.PluginInfo.Company;
                info["description"] = p.Instance.PluginInfo.Description;
                info["name"] = p.Instance.PluginInfo.Name;
                info["title"] = p.Instance.PluginInfo.Title;
                info["version"] = p.Instance.PluginInfo.Version;
                info["website"] = p.Instance.PluginInfo.Website;
                info["filename"] = p.AssemblyPath;
                info["fullname"] = p.FullName;
                this.Interface["exec._tabledata"] = info;
            }
            else {
                this.Interface["exec._tabledata"] = null;
            }
        }
        #endregion

        public PLua Interface { get; set; }

        public bool InitializeScript(string name) {
            if (!this.IsValidLuaScriptName(name)) {
                return false;
            }

            // Load scripts on demmand
            if (this.CallStrings.Contains(name) && !this.Interface.IsSet(name)) {
                string script = Executioner.Directory + "scripts" +
                    Path.DirectorySeparatorChar + name + ".lua";
                this.Interface.LoadFile(script);
            }

            if (this.Interface.IsSet(name)) {
                this.Interface.DoString("exec.scriptdir = [[" + this.ScriptDirectory(name) + "]]");
                return true;
            }

            return false;
        }
        private bool IsValidLuaScriptName(string name) {
            if (!Regex.IsMatch(name, "^[a-zA-Z0-9_]*$") ||
                name == "p" || name == "perseus" || name == "exec"
            ) {
                return false;
            }

            return true;
        }
        private string ScriptDirectory(string name) {
            return Executioner.Directory + "scripts" + Path.DirectorySeparatorChar +
                name + Path.DirectorySeparatorChar;
        }
        
        #region IExecutableCommands
        public List<string> CallStrings { get; set; }
        public ExecutionResult ExecuteCommand(string callString, string parameters) {
            // If lua command
            if (callString == Executioner.Config.GetString("Lua", "Lua Call String", "lua")) {
                var prams = new ParameterInfo(parameters);

                try {
                    string returnValue = string.Empty;
                    object luaResult;

                    if (!prams.IsEmpty) {                        
                        // If the parameters is a lua file
                        if (File.Exists(prams.Parameters[0])) {
                            luaResult = this.Interface.LoadFile(prams.Parameters[0]).ToString();
                        }
                        else { // otherwise assum lua code
                            luaResult = this.Interface.DoString(string.Join(" ", prams.Parameters.ToArray())).ToString();
                        }

                        if (luaResult != null) {
                            returnValue = luaResult.ToString();
                        }
                    }
                    else if (Clipboard.ContainsText()) {
                        luaResult = this.Interface.DoString(Clipboard.GetText()).ToString();
                        
                        if (luaResult != null) {
                            returnValue = luaResult.ToString();
                        }
                    }
                    else if (Clipboard.ContainsFileDropList()) {
                        var files = Clipboard.GetFileDropList();
                        foreach (string file in files) {
                            string tmpResult = string.Empty;

                            luaResult = this.Interface.LoadFile(file).ToString();

                            if (luaResult != null) {
                                tmpResult = luaResult.ToString();
                            }

                            if (returnValue.IsEmpty()) {
                                returnValue = tmpResult;
                            }
                            else if (tmpResult.IsNotEmpty()) {
                                returnValue += Environment.NewLine + tmpResult;
                            }
                        }
                    }

                    if (prams.HasFlags("c")) {
                        Clipboard.SetText(returnValue);
                    }

                    if (prams.HasFlags("d") && returnValue.IsNotEmpty()) {
                        int time;
                        if (prams.Flags["d"].Parameters.Count > 0) {
                            if (!int.TryParse(prams.Flags["d"].Parameters[0], out time)) {
                                time = Executioner.Config.GetInt("Lua", "Display Time", 1500);
                            }
                        }
                        else {
                            time = Executioner.Config.GetInt("Lua", "Display Time", 1500);
                        }

                        if (prams.Flags["d"].Parameters.Count > 1) {
                            Executioner.Display(returnValue, time, prams.Flags["d"].Parameters[1]);
                        }
                        else {
                            Executioner.Display(returnValue, time);
                        }
                    }

                    return ExecutionResult.Success;
                }
                catch {
                    return ExecutionResult.Failure;
                }
            }

            if (this.InitializeScript(callString)) {
                return ((bool)this.Interface.CallFunction(callString + ".execute", parameters)).ToExecutionResult();
            }

            return ExecutionResult.Failure;
        }
        #endregion

        #region ILoader
        public void Load() {
            this.Interface = new PLua();
            this.CallStrings = new List<string>();

            this.CallStrings.Add(Executioner.Config.GetString("Lua", "Lua Call String", "lua"));

            string dir = Executioner.Directory + "scripts" + Path.DirectorySeparatorChar;
            if (Directory.Exists(dir)) {
                var luascripts = from s in Directory.GetFiles(dir)
                                 where Path.GetExtension(s) == ".lua"
                                 select Path.GetFileNameWithoutExtension(s);
                this.CallStrings.AddRange(luascripts);
            }

            if (this.CallStrings.Count > 0) {                
                this.Interface.DoString("exec = {}");

                // Work around for function overloading
                this.Interface.RegisterFunction("exec._display1", this, "LuaDisplay1");
                this.Interface.RegisterFunction("exec._display2", this, "LuaDisplay2");
                this.Interface.RegisterFunction("exec._display3", this, "LuaDisplay3");
                this.Interface.DoString(@"
                    exec.display = function(data, time, display)
                        if time ~= nil then
                            if display ~= nil then
                                exec._display3(data, time, display)
                            else
                                exec._display2(data, time)
                            end
                        else
                            exec._display1(data)
                        end
                    end
                ");

                // Execute function
                this.Interface.RegisterFunction("exec.execute", this, "LuaExecute");

                // Sound functions
                this.Interface.DoString("exec.sounds = {}");
                this.Interface.RegisterFunction("exec.sounds.error", this, "LuaSoundError");
                this.Interface.RegisterFunction("exec.sounds.errornotify", this, "LuaSoundErrorNotify");
                this.Interface.RegisterFunction("exec.sounds.success", this, "LuaSoundSucces");
                this.Interface.RegisterFunction("exec.sounds.successnotify", this, "LuaSoundSuccessNotify");

                // Log function
                this.Interface.RegisterFunction("exec._log1", this, "LuaLog1");
                this.Interface.RegisterFunction("exec._log2", this, "LuaLog2");
                this.Interface.DoString(@"
                    exec.log = function(message, type)
                        if type ~= nil then
                            exec._log2(message, type)                            
                        else
                            exec._log1(message)
                        end
                    end
                ");

                // Version property
                this.Interface["exec.version"] = Executioner.Version;
                
                // Directories
                this.Interface["exec.directory"] = Executioner.Directory;
                this.Interface["exec.plugindirectory"] = Executioner.PluginDirectory;
                this.Interface["exec.scriptdirectory"] = Executioner.ScriptDirectory;
                this.Interface["exec.cachedirectory"] = Executioner.CacheDirectory;

                // Plugin functions
                this.Interface.RegisterFunction("exec.ispluginloaded", this, "LuaIsPluginLoaded");
                this.Interface.RegisterFunction("exec._plugininfo", this, "LuaPluginInfo");
                this.Interface.DoString(@"
                    exec.plugininfo = function(plgugin)
                        exec._plugininfo(plugin)

                        local info = perseus.table.copy(exec._tabledata, true)
                        exec._tabledata = nil

                        return info
                    end
                ");
            }
        }
        public void Unload() {
            this.Interface = null;
        }
        #endregion

        #region IConfigurable<IniFile> Members
        public IConfigurator<IniFile> Configurator {
            get {
                if (this._Configurator == null) {
                    this._Configurator = new LuaConfigurator();
                }

                return this._Configurator;
            }
        }
        #endregion
    }
}
